package pl.ydp.components.mathcell.mathcell.expressions
{
	import pl.ydp.components.mathcell.mathrecognizer.characters.Character;
	import pl.ydp.components.mathcell.mathrecognizer.characters.CharacterType;
	
	/**
	 * Responsible for analyzing the math expressions.
	 * 
	 * Also provides the functionality to analyze fractions.
	 */
	public class MathExpressionProcessor
	{
	
		public static const OPERATOR_DIVIDE_SYNONYMS:Array = new Array("/", ":", "÷");
		public static const OPERATOR_MULTIPLY_SYNONYMS:Array = new Array("*", "·", "×");
		public static const OPERATOR_DIVIDE:String = "/";	
		public static const DIGITS:Array = new Array("0","1","2","3","4","5","6","7","8","9");
		public static const OPERATOR_COMMA:String = ",";
		public static const NUMBER_COMPONENTS:Array = new Array("0","1","2","3","4","5","6","7","8","9",",");
		public static const OPERATOR_FRACTION:String = "|";
	
		public static function findExpressionLength(expression:String):Number{
			
			var count:Number = 0;
			
			expression = replaceDivideOperators(expression);
			
			var firstDivideOperatorFound:Boolean = false;
			var breakFractionAnalysis:Boolean;
			var fractionEndIndex:Number;
			var firstCommaPassed:Boolean = false;
			
			for (var i:Number = 0 ; i < expression.length ; i ++){
				if (expression.charAt(i) == OPERATOR_FRACTION){
					
					var numeratorLength:Number = 0;
					var denominatorLength:Number = 0;
					
					breakFractionAnalysis = false;
					firstDivideOperatorFound = false;
					
					fractionEndIndex = i;
					
					for (var fi:Number = i+1 ; fi < expression.length ; fi ++){
						breakFractionAnalysis = true;
						
						if (DIGITS.indexOf(expression.charAt(fi)) == -1){
							if (!firstDivideOperatorFound  &&  expression.charAt(fi) == OPERATOR_DIVIDE){
								firstDivideOperatorFound = true;
								breakFractionAnalysis = false;
							} else {
								fractionEndIndex = fi;
							}
						} else {
							breakFractionAnalysis = false;
							if (!firstDivideOperatorFound)
								numeratorLength++;
							else
								denominatorLength++;
						}
						
						if (breakFractionAnalysis){
							break;
						}
					}
					
					if (!breakFractionAnalysis){
						fractionEndIndex = expression.length;
					}
					if (fractionEndIndex > i+1){
						count += (numeratorLength > denominatorLength) ? numeratorLength : denominatorLength;
					}
					
					i = fractionEndIndex-1;
					
					if (expression.charAt(fractionEndIndex) == OPERATOR_FRACTION)
						i++;
					
				} else if (expression.charAt(i) == OPERATOR_COMMA){
					 if (!firstCommaPassed){
					 	firstCommaPassed = true;
					 } else {
					 	count++;
					 }
				} else {
					count++;
				}
				
			}
			
			return count;
		}
		
		public static function replaceDivideOperators(expression:String):String{
			
			for (var divop:Number = 1 ; divop < OPERATOR_DIVIDE_SYNONYMS.length ; divop ++){
				while (expression.indexOf(OPERATOR_DIVIDE_SYNONYMS[divop]) != -1){
					expression = expression.replace(OPERATOR_DIVIDE_SYNONYMS[divop], OPERATOR_DIVIDE_SYNONYMS[0]);
				}
			}
			
			return expression;
		}
		
		public static function replaceMultiplyOperators(expression:String):String{
			
			for (var divop:Number = 1 ; divop < OPERATOR_MULTIPLY_SYNONYMS.length ; divop ++){
				while (expression.indexOf(OPERATOR_MULTIPLY_SYNONYMS[divop]) != -1){
					expression = expression.replace(OPERATOR_MULTIPLY_SYNONYMS[divop], OPERATOR_MULTIPLY_SYNONYMS[0]);
				}
			}
			
			return expression;
		}
		
		public static function removeTrailingOperatorFractions(expression:String):String{
			if (expression.indexOf(OPERATOR_FRACTION) == -1)
				return expression;
				
			var output:String = expression;
			var outputNormalized:String = replaceDivideOperators(expression);
			
			var n:Number;
			var lastOFPosition:Number = -1;
			var endingOFPosition:Number;
			var firstDivideOperatorPassed:Boolean;
			var endingOFIsTrailing:Boolean;
			
			while (output.indexOf(OPERATOR_FRACTION,lastOFPosition) != -1){
				lastOFPosition = output.indexOf(OPERATOR_FRACTION,lastOFPosition);
				endingOFPosition = output.indexOf(OPERATOR_FRACTION,lastOFPosition+1);
				
				if (endingOFPosition == -1)
					break;
				
				firstDivideOperatorPassed = false;
				endingOFIsTrailing = true;
				
				for (n = lastOFPosition+1 ; n < endingOFPosition ; n ++){
					if (!firstDivideOperatorPassed  &&  outputNormalized.charAt(n) == OPERATOR_DIVIDE){
						firstDivideOperatorPassed = true;
					} else if (NUMBER_COMPONENTS.indexOf(outputNormalized.charAt(n)) == -1){
						endingOFIsTrailing = false;
						break
					}
				}
				
				if (endingOFIsTrailing){
					var arrHelper:Array = output.split("");
					arrHelper.splice(endingOFPosition, 1);
					output = arrHelper.join("");
					outputNormalized = replaceDivideOperators(output);
				}
				lastOFPosition = endingOFPosition;
				
			}
			
			return output;
		}
		
		public static function normalizeExpression(expression:String):String{
			var output:String = "";
			
			output = replaceDivideOperators(expression);
			output = replaceMultiplyOperators(output);
			output = removeTrailingOperatorFractions(output);
			
			return output;
		}
		
		
		public static function convertToRawExpression(expressionFractions:String):String{
			
			var fractionOpeningStampIndex:Number;
			var fractionClosingStampIndex:Number;
			var fractionBeginIndex:Number;
			var fractionEndIndex:Number;
			var firstDivideOperatorFound:Boolean;
			var currChar:Character;
			var expressionArray:Array;
			var c:Number;
			
			expressionFractions = replaceDivideOperators(expressionFractions);
			expressionArray = expressionFractions.split("");
			
			// replace divide operators
			
			while (expressionArray.indexOf(String.fromCharCode(Character.SPECIAL_FRACTION)) != -1){
				fractionOpeningStampIndex = expressionArray.indexOf(String.fromCharCode(Character.SPECIAL_FRACTION));
				
				// insert bracket before the integer
				
				fractionBeginIndex = 0;
				
				for (c = fractionOpeningStampIndex - 1 ; c >= 0 ; c --){
					if ((new Character(expressionArray[c])).type == CharacterType.DIGIT){
						continue;
					} else {
						fractionBeginIndex = c+1;
						break;
					}
				}
				
				firstDivideOperatorFound = false;
				fractionClosingStampIndex = -1;
				fractionEndIndex = expressionArray.length;
				
				// insert bracket after the fraction
								
				for (c = fractionOpeningStampIndex + 1 ; c < expressionArray.length ; c ++ ){
					
					currChar = new Character(expressionArray[c]);
					
					if (currChar.type == CharacterType.DIGIT){
						continue;
					} else if (currChar.symbol == String.fromCharCode(Character.SPECIAL_FRACTION)  &&  fractionClosingStampIndex == -1){
						fractionClosingStampIndex = c;
						fractionEndIndex = c;
						break;
					} else if (currChar.type == CharacterType.OPERATOR  &&  currChar.symbol == OPERATOR_DIVIDE_SYNONYMS[0]  &&  !firstDivideOperatorFound){
						firstDivideOperatorFound = true;
					} else {
						fractionEndIndex = c;
						break;
					}
				}
				
				
				if (fractionClosingStampIndex != -1)
					expressionArray.splice(fractionClosingStampIndex,1, ")");
				else
					expressionArray.splice(fractionEndIndex,0, ")");
				
				if (fractionBeginIndex < fractionOpeningStampIndex){
					expressionArray.splice(fractionOpeningStampIndex, 1, "+");
					expressionArray.splice(fractionBeginIndex, 0, "(");
				} else{
					expressionArray.splice(fractionOpeningStampIndex, 1, "(");
				}
				
				
				
			}
			
			return expressionArray.join("");
		}	
		
		public static function containsFraction(expr:String):Boolean{
			return (expr.indexOf(OPERATOR_FRACTION) != -1);
		}
		
		public static function splitComponents(expression:String):Array{
			
			var expressionNormalized:String = replaceDivideOperators(expression);
			
			var components:Array = new Array;
			var firstOperatorDividePassed:Boolean;
			var n:Number;
			var nf:Number;
			
			
			for (n = 0 ; n < expressionNormalized.length ; n ++){
				if (expressionNormalized.charAt(n) == OPERATOR_FRACTION){
					
					firstOperatorDividePassed = false;
					
					for (nf = n+1 ; nf < expressionNormalized.length ; nf ++){
						if (!firstOperatorDividePassed  &&  expressionNormalized.charAt(nf) == OPERATOR_DIVIDE){
							firstOperatorDividePassed = true;
						} else if (NUMBER_COMPONENTS.indexOf(expressionNormalized.charAt(nf)) != -1){
							
						} else {
							break;
						}
					}
					
					var fractionEndIndex:Number = nf;
					
					if (expressionNormalized.charAt(nf) == OPERATOR_FRACTION)
						fractionEndIndex++;
					
					components.push(expression.substring(n, fractionEndIndex));
					
					n = fractionEndIndex-1;
					
				} else {
					components.push(expression.charAt(n));
				}
			}
			
			return components;
			
		}
		
		public static function compare(ref:String, test:String, allowFractionReduction:Boolean = false):Boolean{
			if (ref == test)
				return true;
				
			var refNormalized:String = normalizeExpression(ref);
			var testNormalized:String = normalizeExpression(test);
			
			if (refNormalized == testNormalized)
				return true;
						
			if (allowFractionReduction)
				return compareWithFractionReduction(refNormalized, testNormalized);
				
			return false;
		}
		
		private static function compareWithFractionReduction(ref:String, test:String):Boolean{
			
			var inFraction:Boolean = false;
			var firstDivideOperatorPassed:Boolean = false;			
			var numerator_ref:String = "";
			var denominator_ref:String = "";			
			var numerator_test:String = "";
			var denominator_test:String = "";
			
			var i:Number;
			var i_ref:Number = 0;
			var i_test:Number = 0;
			
			while (i_ref < ref.length  && i_test < test.length){
				if (!inFraction){
					if (ref.charAt(i_ref) != test.charAt(i_test)){
						return false;
					} else if (ref.charAt(i_ref) == OPERATOR_FRACTION){
						inFraction = true;
					}
					i_ref++;
					i_test++;
				} else if (inFraction){
					
					firstDivideOperatorPassed = false;
					numerator_ref = "";
					denominator_ref = "";
					for (i = i_ref ; i < ref.length ; i ++){
						if (ref.charAt(i) == OPERATOR_DIVIDE){
							if (!firstDivideOperatorPassed) {
								firstDivideOperatorPassed = true;
							}else{
								break;
							}
						} else if(DIGITS.indexOf(ref.charAt(i)) == -1){
							break;
						} else {
							if (!firstDivideOperatorPassed){
								numerator_ref += ref.charAt(i);
							} else {
								denominator_ref += ref.charAt(i);
							} 
						}	
					} 
					i_ref = i;
										
					firstDivideOperatorPassed = false;
					numerator_test = "";
					denominator_test = "";
					for (i = i_test ; i < test.length ; i ++){
						if (test.charAt(i) == OPERATOR_DIVIDE){
							if (!firstDivideOperatorPassed) {
								firstDivideOperatorPassed = true;
							}else{
								break;
							}
						} else if(DIGITS.indexOf(test.charAt(i)) == -1){
							break;
						} else {
							if (!firstDivideOperatorPassed){
								numerator_test += test.charAt(i);
							} else {
								denominator_test += test.charAt(i);
							} 
						}	
					} 
					i_test = i;
					
					if (numerator_ref == ""  ||  denominator_ref == ""  ||  numerator_test == ""  ||  denominator_test == ""){
						return false;
					}
					
					var multiplyer_num:Number = (parseInt(numerator_ref) / parseInt(numerator_test) >= 1) ? parseInt(numerator_ref) / parseInt(numerator_test) : parseInt(numerator_test) / parseInt(numerator_ref);
					var multiplyer_den:Number = (parseInt(denominator_ref) / parseInt(denominator_test) >= 1) ? parseInt(denominator_ref) / parseInt(denominator_test) : parseInt(denominator_test) / parseInt(denominator_ref);
					
					if (multiplyer_num != multiplyer_den)
						return false;
						
				}
			} 
			
			if (i_ref >= ref.length-1  && i_test >= test.length-1)
				return true;
			
			return false;
		}
		
	}
}